<!DOCTYPE html>



  


<html class="theme-next gemini use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.8.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="java,">










<meta name="description" content="深入探索 Java 热部署丁 志君2013 年 9 月 23 日发布 简介在 Java 开发领域，热部署一直是一个难以解决的问题，目前的 Java 虚拟机只能实现方法体的修改热部署，对于整个类的结构修改，仍然需要重启虚拟机，对类重新加载才能完成更新操作。对于某些大型的应用来说，每次的重启都需要花费大量的时间成本。虽然 osgi 架构的出现，让模块重启成为可能，但是如果模块之间有调用关系的话，这样的">
<meta name="keywords" content="java">
<meta property="og:type" content="article">
<meta property="og:title" content="深入探索 Java 热部署">
<meta property="og:url" content="https://blog.xiezc.xyz/2019/01/31/java热部署/index.html">
<meta property="og:site_name" content="xiezc的小站">
<meta property="og:description" content="深入探索 Java 热部署丁 志君2013 年 9 月 23 日发布 简介在 Java 开发领域，热部署一直是一个难以解决的问题，目前的 Java 虚拟机只能实现方法体的修改热部署，对于整个类的结构修改，仍然需要重启虚拟机，对类重新加载才能完成更新操作。对于某些大型的应用来说，每次的重启都需要花费大量的时间成本。虽然 osgi 架构的出现，让模块重启成为可能，但是如果模块之间有调用关系的话，这样的">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://www.ibm.com/developerworks/cn/java/j-lo-hotdeploy/image001.png">
<meta property="og:image" content="https://www.ibm.com/developerworks/cn/java/j-lo-hotdeploy/image002.png">
<meta property="og:updated_time" content="2019-04-04T14:42:40.497Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="深入探索 Java 热部署">
<meta name="twitter:description" content="深入探索 Java 热部署丁 志君2013 年 9 月 23 日发布 简介在 Java 开发领域，热部署一直是一个难以解决的问题，目前的 Java 虚拟机只能实现方法体的修改热部署，对于整个类的结构修改，仍然需要重启虚拟机，对类重新加载才能完成更新操作。对于某些大型的应用来说，每次的重启都需要花费大量的时间成本。虽然 osgi 架构的出现，让模块重启成为可能，但是如果模块之间有调用关系的话，这样的">
<meta name="twitter:image" content="https://www.ibm.com/developerworks/cn/java/j-lo-hotdeploy/image001.png">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Gemini',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://blog.xiezc.xyz/2019/01/31/java热部署/">





  <title>深入探索 Java 热部署 | xiezc的小站</title>
  





  <script type="text/javascript">
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?8ae02d8cf311eabbae2621ff2d51a260";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>




</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">xiezc的小站</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">xiezc的小站</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br>
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            归档
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://blog.xiezc.xyz/2019/01/31/java热部署/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="xiezc">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="xiezc的小站">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">深入探索 Java 热部署</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2019-01-31T09:30:36+08:00">
                2019-01-31
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/java/" itemprop="url" rel="index">
                    <span itemprop="name">java</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/2019/01/31/java热部署/" class="leancloud_visitors" data-flag-title="深入探索 Java 热部署">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h1 id="深入探索-Java-热部署"><a href="#深入探索-Java-热部署" class="headerlink" title="深入探索 Java 热部署"></a>深入探索 Java 热部署</h1><p>丁 志君<br>2013 年 9 月 23 日发布</p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>在 Java 开发领域，热部署一直是一个难以解决的问题，目前的 Java 虚拟机只能实现方法体的修改热部署，对于整个类的结构修改，仍然需要重启虚拟机，对类重新加载才能完成更新操作。对于某些大型的应用来说，每次的重启都需要花费大量的时间成本。虽然 osgi 架构的出现，让模块重启成为可能，但是如果模块之间有调用关系的话，这样的操作依然会让应用出现短暂的功能性休克。本文将探索如何在不破坏 Java 虚拟机现有行为的前提下，实现某个单一类的热部署，让系统无需重启就完成某个类的更新。</p>
<h2 id="类加载的探索"><a href="#类加载的探索" class="headerlink" title="类加载的探索"></a>类加载的探索</h2><p>首先谈一下何为热部署（hotswap），热部署是在不重启 Java 虚拟机的前提下，能自动侦测到 class 文件的变化，更新运行时 class 的行为。Java 类是通过 Java 虚拟机加载的，某个类的 class 文件在被 classloader 加载后，会生成对应的 Class 对象，之后就可以创建该类的实例。默认的虚拟机行为只会在启动时加载类，如果后期有一个类需要更新的话，单纯替换编译的 class 文件，Java 虚拟机是不会更新正在运行的 class。如果要实现热部署，最根本的方式是修改虚拟机的源代码，改变 classloader 的加载行为，使虚拟机能监听 class 文件的更新，重新加载 class 文件，这样的行为破坏性很大，为后续的 JVM 升级埋下了一个大坑。</p>
<p>另一种友好的方法是创建自己的 classloader 来加载需要监听的 class，这样就能控制类加载的时机，从而实现热部署。本文将具体探索如何实现这个方案。首先需要了解一下 Java 虚拟机现有的加载机制。目前的加载机制，称为双亲委派，系统在使用一个 classloader 来加载类时，会先询问当前 classloader 的父类是否有能力加载，如果父类无法实现加载操作，才会将任务下放到该 classloader 来加载。这种自上而下的加载方式的好处是，让每个 classloader 执行自己的加载任务，不会重复加载类。但是这种方式却使加载顺序非常难改变，让自定义 classloader 抢先加载需要监听改变的类成为了一个难题。</p>
<p>不过我们可以换一个思路，虽然无法抢先加载该类，但是仍然可以用自定义 classloader 创建一个功能相同的类，让每次实例化的对象都指向这个新的类。当这个类的 class 文件发生改变的时候，再次创建一个更新的类，之后如果系统再次发出实例化请求，创建的对象讲指向这个全新的类。</p>
<p>下面来简单列举一下需要做的工作。</p>
<ul>
<li>创建自定义的 classloader，加载需要监听改变的类，在 class 文件发生改变的时候，重新加载该类。</li>
<li>改变创建对象的行为，使他们在创建时使用自定义 classloader 加载的 class。</li>
</ul>
<h2 id="自定义加载器的实现"><a href="#自定义加载器的实现" class="headerlink" title="自定义加载器的实现"></a>自定义加载器的实现</h2><p>自定义加载器仍然需要执行类加载的功能。这里却存在一个问题，同一个类加载器无法同时加载两个相同名称的类，由于不论类的结构如何发生变化，生成的类名不会变，而 classloader 只能在虚拟机停止前销毁已经加载的类，这样 classloader 就无法加载更新后的类了。这里有一个小技巧，让每次加载的类都保存成一个带有版本信息的 class，比如加载 Test.class 时，保存在内存中的类是 Test_v1.class，当类发生改变时，重新加载的类名是 Test_v2.class。但是真正执行加载 class 文件创建 class 的 defineClass 方法是一个 native 的方法，修改起来又变得很困难。所以面前还剩一条路，那就是直接修改编译生成的 class 文件。</p>
<h3 id="利用-ASM-修改-class-文件"><a href="#利用-ASM-修改-class-文件" class="headerlink" title="利用 ASM 修改 class 文件"></a>利用 ASM 修改 class 文件</h3><p>可以修改字节码的框架有很多，比如 ASM，CGLIB。本文使用的是 ASM。先来介绍一下 class 文件的结构，class 文件包含了以下几类信息，一个是类的基本信息，包含了访问权限信息，类名信息，父类信息，接口信息。第二个是类的变量信息。第三个是方法的信息。ASM 会先加载一个 class 文件，然后严格顺序读取类的各项信息，用户可以按照自己的意愿定义增强组件修改这些信息，最后输出成一个新的 class。</p>
<p>首先看一下如何利用 ASM 修改类信息。</p>
<h5 id="清单-1-利用-ASM-修改字节码"><a href="#清单-1-利用-ASM-修改字节码" class="headerlink" title="清单 1. 利用 ASM 修改字节码"></a>清单 1. 利用 ASM 修改字节码</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">ClassWriter cw = <span class="keyword">new</span> ClassWriter(ClassWriter.COMPUTE_MAXS); </span><br><span class="line">ClassReader cr = <span class="keyword">null</span>;     </span><br><span class="line">String enhancedClassName = classSource.getEnhancedName(); </span><br><span class="line"><span class="keyword">try</span> &#123; </span><br><span class="line">    cr = <span class="keyword">new</span> ClassReader(<span class="keyword">new</span> FileInputStream( </span><br><span class="line">            classSource.getFile())); </span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException e) &#123; </span><br><span class="line">    e.printStackTrace(); </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>; </span><br><span class="line">&#125; </span><br><span class="line">ClassVisitor cv = <span class="keyword">new</span> EnhancedModifier(cw, </span><br><span class="line">        className.replace(<span class="string">"."</span>, <span class="string">"/"</span>), </span><br><span class="line">        enhancedClassName.replace(<span class="string">"."</span>, <span class="string">"/"</span>)); </span><br><span class="line">cr.accept(cv, <span class="number">0</span>);</span><br></pre></td></tr></table></figure>
<p>ASM 修改字节码文件的流程是一个责任链模式，首先使用一个 ClassReader 读入字节码，然后利用 ClassVisitor 做个性化的修改，最后利用 ClassWriter 输出修改后的字节码。</p>
<p>之前提过，需要将读取的 class 文件的类名做一些修改，加载成一个全新名字的派生类。这里将之分为了 2 个步骤。</p>
<p>第一步，先将原来的类变成接口。</p>
<h5 id="清单-2-重定义的原始类"><a href="#清单-2-重定义的原始类" class="headerlink" title="清单 2. 重定义的原始类"></a>清单 2. 重定义的原始类</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> Class&lt;?&gt; redefineClass(String className)&#123; </span><br><span class="line">       ClassWriter cw = <span class="keyword">new</span> ClassWriter(ClassWriter.COMPUTE_MAXS); </span><br><span class="line">       ClassReader cr = <span class="keyword">null</span>; </span><br><span class="line">       ClassSource cs = classFiles.get(className); </span><br><span class="line">       <span class="keyword">if</span>(cs==<span class="keyword">null</span>)&#123; </span><br><span class="line">           <span class="keyword">return</span> <span class="keyword">null</span>; </span><br><span class="line">       &#125; </span><br><span class="line">       <span class="keyword">try</span> &#123; </span><br><span class="line">           cr = <span class="keyword">new</span> ClassReader(<span class="keyword">new</span> FileInputStream(cs.getFile())); </span><br><span class="line">       &#125; <span class="keyword">catch</span> (IOException e) &#123; </span><br><span class="line">           e.printStackTrace(); </span><br><span class="line">           <span class="keyword">return</span> <span class="keyword">null</span>; </span><br><span class="line">       &#125; </span><br><span class="line">       ClassModifier cm = <span class="keyword">new</span> ClassModifier(cw); </span><br><span class="line">       cr.accept(cm, <span class="number">0</span>); </span><br><span class="line">       <span class="keyword">byte</span>[] code = cw.toByteArray(); </span><br><span class="line">       <span class="keyword">return</span> defineClass(className, code, <span class="number">0</span>, code.length); </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>首先 load 原始类的 class 文件，此处定义了一个增强组件 <code>ClassModifier</code>，作用是修改原始类的类型，将它转换成接口。原始类的所有方法逻辑都会被去掉。</p>
<p>第二步，生成的派生类都实现这个接口，即原始类，并且复制原始类中的所有方法逻辑。之后如果该类需要更新，会生成一个新的派生类，也会实现这个接口。这样做的目的是不论如何修改，同一个 class 的派生类都有一个共同的接口，他们之间的转换变得对外不透明。</p>
<h5 id="清单-3-定义一个派生类"><a href="#清单-3-定义一个派生类" class="headerlink" title="清单 3. 定义一个派生类"></a>清单 3. 定义一个派生类</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在 class 文件发生改变时重新定义这个类</span></span><br><span class="line">   <span class="keyword">private</span> Class&lt;?&gt; redefineClass(String className, ClassSource classSource)&#123; </span><br><span class="line">       ClassWriter cw = <span class="keyword">new</span> ClassWriter(ClassWriter.COMPUTE_MAXS); </span><br><span class="line">       ClassReader cr = <span class="keyword">null</span>; </span><br><span class="line">       classSource.update(); </span><br><span class="line">       String enhancedClassName = classSource.getEnhancedName();       </span><br><span class="line">       <span class="keyword">try</span> &#123; </span><br><span class="line">           cr = <span class="keyword">new</span> ClassReader( </span><br><span class="line">                   <span class="keyword">new</span> FileInputStream(classSource.getFile())); </span><br><span class="line">       &#125; <span class="keyword">catch</span> (IOException e) &#123; </span><br><span class="line">           e.printStackTrace(); </span><br><span class="line">           <span class="keyword">return</span> <span class="keyword">null</span>; </span><br><span class="line">       &#125; </span><br><span class="line">       EnhancedModifier em = <span class="keyword">new</span> EnhancedModifier(cw, className.replace(<span class="string">"."</span>, <span class="string">"/"</span>), </span><br><span class="line">               enhancedClassName.replace(<span class="string">"."</span>, <span class="string">"/"</span>)); </span><br><span class="line">       ExtendModifier exm = <span class="keyword">new</span> ExtendModifier(em, className.replace(<span class="string">"."</span>, <span class="string">"/"</span>), </span><br><span class="line">               enhancedClassName.replace(<span class="string">"."</span>, <span class="string">"/"</span>)); </span><br><span class="line">       cr.accept(exm, <span class="number">0</span>); </span><br><span class="line">       <span class="keyword">byte</span>[] code = cw.toByteArray(); </span><br><span class="line">       classSource.setByteCopy(code); </span><br><span class="line">       Class&lt;?&gt; clazz = defineClass(enhancedClassName, code, <span class="number">0</span>, code.length); </span><br><span class="line">       classSource.setClassCopy(clazz); </span><br><span class="line">       <span class="keyword">return</span> clazz; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>再次 load 原始类的 class 文件，此处定义了两个增强组件，一个是 <code>EnhancedModifier</code>，这个增强组件的作用是改变原有的类名。第二个增强组件是 <code>ExtendModifier</code>，这个增强组件的作用是改变原有类的父类，让这个修改后的派生类能够实现同一个原始类（此时原始类已经转成接口了）。</p>
<p>自定义 classloader 还有一个作用是监听会发生改变的 class 文件，classloader 会管理一个定时器，定时依次扫描这些 class 文件是否改变。</p>
<h2 id="改变创建对象的行为"><a href="#改变创建对象的行为" class="headerlink" title="改变创建对象的行为"></a>改变创建对象的行为</h2><p>Java 虚拟机常见的创建对象的方法有两种，一种是静态创建，直接 new 一个对象，一种是动态创建，通过反射的方法，创建对象。</p>
<p>由于已经在自定义加载器中更改了原有类的类型，把它从类改成了接口，所以这两种创建方法都无法成立。我们要做的是将实例化原始类的行为变成实例化派生类。</p>
<p>对于第一种方法，需要做的是将静态创建，变为通过 classloader 获取 class，然后动态创建该对象。</p>
<h5 id="清单-4-替换后的指令集所对应的逻辑"><a href="#清单-4-替换后的指令集所对应的逻辑" class="headerlink" title="清单 4. 替换后的指令集所对应的逻辑"></a>清单 4. 替换后的指令集所对应的逻辑</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 原始逻辑   </span></span><br><span class="line">  Greeter p = <span class="keyword">new</span> Greeter(); </span><br><span class="line"><span class="comment">// 改变后的逻辑</span></span><br><span class="line">  IGreeter p = (IGreeter)MyClassLoader.getInstance().</span><br><span class="line">  findClass(<span class="string">"com.example.Greeter"</span>).newInstance();</span><br></pre></td></tr></table></figure>
<p>这里又需要用到 ASM 来修改 class 文件了。查找到所有 new 对象的语句，替换成通过 classloader 的形式来获取对象的形式。</p>
<h5 id="清单-5-利用-ASM-修改方法体"><a href="#清单-5-利用-ASM-修改方法体" class="headerlink" title="清单 5. 利用 ASM 修改方法体"></a>清单 5. 利用 ASM 修改方法体</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span> </span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">visitTypeInsn</span><span class="params">(<span class="keyword">int</span> opcode, String type)</span> </span>&#123; </span><br><span class="line">    <span class="keyword">if</span>(opcode==Opcodes.NEW &amp;&amp; type.equals(className))&#123; </span><br><span class="line">        List&lt;LocalVariableNode&gt; variables = node.localVariables; </span><br><span class="line">        String compileType = <span class="keyword">null</span>; </span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i&lt;variables.size();i++)&#123; </span><br><span class="line">            LocalVariableNode localVariable = variables.get(i); </span><br><span class="line">            compileType = formType(localVariable.desc); </span><br><span class="line">            <span class="keyword">if</span>(matchType(compileType)&amp;&amp;!valiableIndexUsed[i])&#123; </span><br><span class="line">                valiableIndexUsed[i] = <span class="keyword">true</span>; </span><br><span class="line">                <span class="keyword">break</span>; </span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    mv.visitMethodInsn(Opcodes.INVOKESTATIC, CLASSLOAD_TYPE, </span><br><span class="line">        <span class="string">"getInstance"</span>, <span class="string">"()L"</span>+CLASSLOAD_TYPE+<span class="string">";"</span>); </span><br><span class="line">    mv.visitLdcInsn(type.replace(<span class="string">"/"</span>, <span class="string">"."</span>)); </span><br><span class="line">    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLASSLOAD_TYPE, </span><br><span class="line">        <span class="string">"findClass"</span>, <span class="string">"(Ljava/lang/String;)Ljava/lang/Class;"</span>); </span><br><span class="line">    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, <span class="string">"java/lang/Class"</span>, </span><br><span class="line">        <span class="string">"newInstance"</span>, <span class="string">"()Ljava/lang/Object;"</span>); </span><br><span class="line">    mv.visitTypeInsn(Opcodes.CHECKCAST, compileType); </span><br><span class="line">    flag = <span class="keyword">true</span>; </span><br><span class="line">    &#125; <span class="keyword">else</span> &#123; </span><br><span class="line">        mv.visitTypeInsn(opcode, type); </span><br><span class="line">    &#125; </span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<p>对于第二种创建方法，需要通过修改 <code>Class.forName()</code>和 <code>ClassLoader.findClass()</code>的行为，使他们通过自定义加载器加载类。</p>
<h2 id="使用-JavaAgent-拦截默认加载器的行为"><a href="#使用-JavaAgent-拦截默认加载器的行为" class="headerlink" title="使用 JavaAgent 拦截默认加载器的行为"></a>使用 JavaAgent 拦截默认加载器的行为</h2><p>之前实现的类加载器已经解决了热部署所需要的功能，可是 JVM 启动时，并不会用自定义的加载器加载 classpath 下的所有 class 文件，取而代之的是通过应用加载器去加载。如果在其之后用自定义加载器重新加载已经加载的 class，有可能会出现 LinkageError 的 exception。所以必须在应用启动之前，重新替换已经加载的 class。如果在 jdk1.4 之前，能使用的方法只有一种，改变 jdk 中 classloader 的加载行为，使它指向自定义加载器的加载行为。好在 jdk5.0 之后，我们有了另一种侵略性更小的办法，这就是 JavaAgent 方法，JavaAgent 可以在 JVM 启动之后，应用启动之前的短暂间隙，提供空间给用户做一些特殊行为。比较常见的应用，是利用 JavaAgent 做面向方面的编程，在方法间加入监控日志等。</p>
<p>JavaAgent 的实现很容易，只要在一个类里面，定义一个 premain 的方法。</p>
<h5 id="清单-6-一个简单的-JavaAgent"><a href="#清单-6-一个简单的-JavaAgent" class="headerlink" title="清单 6. 一个简单的 JavaAgent"></a>清单 6. 一个简单的 JavaAgent</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ReloadAgent</span> </span>&#123; </span><br><span class="line">   <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">premain</span><span class="params">(String agentArgs, Instrumentation inst)</span></span>&#123; </span><br><span class="line">       GeneralTransformer trans = <span class="keyword">new</span> GeneralTransformer(); </span><br><span class="line">       inst.addTransformer(trans); </span><br><span class="line">   &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>然后编写一个 manifest 文件，将 <code>Premain-Class</code>属性设置成定义一个拥有 <code>premain</code>方法的类名即可。</p>
<p>生成一个包含这个 manifest 文件的 jar 包。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">manifest-Version: 1.0 </span><br><span class="line">Premain-Class: com.example.ReloadAgent </span><br><span class="line">Can-Redefine-Classes: true</span><br></pre></td></tr></table></figure>
<p>最后需要在执行应用的参数中增加 <code>-javaagent</code>参数 , 加入这个 jar。同时可以为 <code>Javaagent</code>增加参数，下图中的参数是测试代码中 test project 的绝对路径。这样在执行应用的之前，会优先执行 <code>premain</code>方法中的逻辑，并且预解析需要加载的 class。</p>
<h5 id="图-1-增加执行参数"><a href="#图-1-增加执行参数" class="headerlink" title="图 1. 增加执行参数"></a>图 1. 增加执行参数</h5><p><img src="https://www.ibm.com/developerworks/cn/java/j-lo-hotdeploy/image001.png" alt="å¾ 1. å¢å æ§è¡åæ°"></p>
<p>这里利用 <code>JavaAgent</code>替换原始字节码，阻止原始字节码被 Java 虚拟机加载。只需要实现 <code>一个 ClassFileTransformer</code>的接口，利用这个实现类完成 class 替换的功能。</p>
<h5 id="清单-7-替换-class"><a href="#清单-7-替换-class" class="headerlink" title="清单 7. 替换 class"></a>清单 7. 替换 class</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span> </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">byte</span> [] transform(ClassLoader paramClassLoader, String paramString, </span><br><span class="line">     Class&lt;?&gt; paramClass, ProtectionDomain paramProtectionDomain, </span><br><span class="line">     <span class="keyword">byte</span> [] paramArrayOfByte) <span class="keyword">throws</span> IllegalClassFormatException &#123; </span><br><span class="line">    String className = paramString.replace(<span class="string">"/"</span>, <span class="string">"."</span>); </span><br><span class="line">    <span class="keyword">if</span>(className.equals(<span class="string">"com.example.Test"</span>))&#123; </span><br><span class="line">        MyClassLoader cl = MyClassLoader.getInstance(); </span><br><span class="line">        cl.defineReference(className, <span class="string">"com.example.Greeter"</span>); </span><br><span class="line">        <span class="keyword">return</span> cl.getByteCode(className); </span><br><span class="line">    &#125;<span class="keyword">else</span> <span class="keyword">if</span>(className.equals(<span class="string">"com.example.Greeter"</span>))&#123; </span><br><span class="line">        MyClassLoader cl = MyClassLoader.getInstance(); </span><br><span class="line">        cl.redefineClass(className); </span><br><span class="line">        <span class="keyword">return</span> cl.getByteCode(className); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">null</span>; </span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<p>至此，所有的工作大功告成，欣赏一下 hotswap 的结果吧。</p>
<h5 id="图-2-Test-执行结果"><a href="#图-2-Test-执行结果" class="headerlink" title="图 2. Test 执行结果"></a>图 2. Test 执行结果</h5><p><img src="https://www.ibm.com/developerworks/cn/java/j-lo-hotdeploy/image002.png" alt="å¾ 2. Test æ§è¡ç&quot;æ"></p>
<h2 id="结束语"><a href="#结束语" class="headerlink" title="结束语"></a>结束语</h2><p>解决 hotswap 是个困难的课题，本文解决的仅仅是让新实例化的对象使用新的逻辑，并不能改变已经实例化对象的行为，如果 JVM 能够重新设计 class 的生命周期，支持运行时重新更新一个 class，hotswap 就会成为 Java 的一个闪亮新特性。官方的 JVM 一直没有解决热部署这个问题，可能也是由于无法完全克服其中的诸多难点，希望未来的 Jdk 能解决这个问题，让 Java 应用对于更新更友好，避免不断重启应用浪费的时间。</p>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/java/" rel="tag"># java</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2019/01/30/java的spi加载机制/" rel="next" title="java的spi加载机制 JDNI">
                <i class="fa fa-chevron-left"></i> java的spi加载机制 JDNI
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2019/01/31/JVMTI和Agent实现/" rel="prev" title="JVMTI 和 Agent 实现">
                JVMTI 和 Agent 实现 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">xiezc</p>
              <p class="site-description motion-element" itemprop="description">主要一些技术文章, 有些文章是转载的</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">72</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                
                  <span class="site-state-item-count">1</span>
                  <span class="site-state-item-name">分类</span>
                
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                
                  <span class="site-state-item-count">20</span>
                  <span class="site-state-item-name">标签</span>
                
              </div>
            

          </nav>

          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#深入探索-Java-热部署"><span class="nav-number">1.</span> <span class="nav-text">深入探索 Java 热部署</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#简介"><span class="nav-number">1.1.</span> <span class="nav-text">简介</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#类加载的探索"><span class="nav-number">1.2.</span> <span class="nav-text">类加载的探索</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#自定义加载器的实现"><span class="nav-number">1.3.</span> <span class="nav-text">自定义加载器的实现</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#利用-ASM-修改-class-文件"><span class="nav-number">1.3.1.</span> <span class="nav-text">利用 ASM 修改 class 文件</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#清单-1-利用-ASM-修改字节码"><span class="nav-number">1.3.1.0.1.</span> <span class="nav-text">清单 1. 利用 ASM 修改字节码</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#清单-2-重定义的原始类"><span class="nav-number">1.3.1.0.2.</span> <span class="nav-text">清单 2. 重定义的原始类</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#清单-3-定义一个派生类"><span class="nav-number">1.3.1.0.3.</span> <span class="nav-text">清单 3. 定义一个派生类</span></a></li></ol></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#改变创建对象的行为"><span class="nav-number">1.4.</span> <span class="nav-text">改变创建对象的行为</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#清单-4-替换后的指令集所对应的逻辑"><span class="nav-number">1.4.0.0.1.</span> <span class="nav-text">清单 4. 替换后的指令集所对应的逻辑</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#清单-5-利用-ASM-修改方法体"><span class="nav-number">1.4.0.0.2.</span> <span class="nav-text">清单 5. 利用 ASM 修改方法体</span></a></li></ol></li></ol><li class="nav-item nav-level-2"><a class="nav-link" href="#使用-JavaAgent-拦截默认加载器的行为"><span class="nav-number">1.5.</span> <span class="nav-text">使用 JavaAgent 拦截默认加载器的行为</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#清单-6-一个简单的-JavaAgent"><span class="nav-number">1.5.0.0.1.</span> <span class="nav-text">清单 6. 一个简单的 JavaAgent</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#图-1-增加执行参数"><span class="nav-number">1.5.0.0.2.</span> <span class="nav-text">图 1. 增加执行参数</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#清单-7-替换-class"><span class="nav-number">1.5.0.0.3.</span> <span class="nav-text">清单 7. 替换 class</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#图-2-Test-执行结果"><span class="nav-number">1.5.0.0.4.</span> <span class="nav-text">图 2. Test 执行结果</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#结束语"><span class="nav-number">1.6.</span> <span class="nav-text">结束语</span></a></li></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2019</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">xiezc</span>

  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Gemini</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  





  

  
  <script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.4.js"></script>
  <script>AV.initialize("eRAHvO0xVYwckgdXIVpfugRG-gzGzoHsz", "57lKp708hde92YeG1xX9MQj1");</script>
  <script>
    function showTime(Counter) {
      var query = new AV.Query(Counter);
      var entries = [];
      var $visitors = $(".leancloud_visitors");

      $visitors.each(function () {
        entries.push( $(this).attr("id").trim() );
      });

      query.containedIn('url', entries);
      query.find()
        .done(function (results) {
          var COUNT_CONTAINER_REF = '.leancloud-visitors-count';

          if (results.length === 0) {
            $visitors.find(COUNT_CONTAINER_REF).text(0);
            return;
          }

          for (var i = 0; i < results.length; i++) {
            var item = results[i];
            var url = item.get('url');
            var time = item.get('time');
            var element = document.getElementById(url);

            $(element).find(COUNT_CONTAINER_REF).text(time);
          }
          for(var i = 0; i < entries.length; i++) {
            var url = entries[i];
            var element = document.getElementById(url);
            var countSpan = $(element).find(COUNT_CONTAINER_REF);
            if( countSpan.text() == '') {
              countSpan.text(0);
            }
          }
        })
        .fail(function (object, error) {
          console.log("Error: " + error.code + " " + error.message);
        });
    }

    function addCount(Counter) {
      var $visitors = $(".leancloud_visitors");
      var url = $visitors.attr('id').trim();
      var title = $visitors.attr('data-flag-title').trim();
      var query = new AV.Query(Counter);

      query.equalTo("url", url);
      query.find({
        success: function(results) {
          if (results.length > 0) {
            var counter = results[0];
            counter.fetchWhenSave(true);
            counter.increment("time");
            counter.save(null, {
              success: function(counter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(counter.get('time'));
              },
              error: function(counter, error) {
                console.log('Failed to save Visitor num, with error message: ' + error.message);
              }
            });
          } else {
            var newcounter = new Counter();
            /* Set ACL */
            var acl = new AV.ACL();
            acl.setPublicReadAccess(true);
            acl.setPublicWriteAccess(true);
            newcounter.setACL(acl);
            /* End Set ACL */
            newcounter.set("title", title);
            newcounter.set("url", url);
            newcounter.set("time", 1);
            newcounter.save(null, {
              success: function(newcounter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(newcounter.get('time'));
              },
              error: function(newcounter, error) {
                console.log('Failed to create');
              }
            });
          }
        },
        error: function(error) {
          console.log('Error:' + error.code + " " + error.message);
        }
      });
    }

    $(function() {
      var Counter = AV.Object.extend("Counter");
      if ($('.leancloud_visitors').length == 1) {
        addCount(Counter);
      } else if ($('.post-title-link').length > 1) {
        showTime(Counter);
      }
    });
  </script>



  

  
<script>
(function(){
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
        bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';        
    }
    else {
        bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
})();
</script>


  
  

  

  

  

</body>
</html>
